package com.h2.ref.server.web.social;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.ConnectionKey;
import org.springframework.util.MultiValueMap;

import com.h2.ref.server.user.dao.UserDaoXml;
import com.h2.ref.server.user.model.User;
import com.h2.util.resource.IResourceManager;
import com.thoughtworks.xstream.XStream;

public class ConnectionRepositoryXML extends ConnectionRepositoryInMem {
   
   private static final Logger LOG = LoggerFactory.getLogger(
         UserDaoXml.class);
   
   ////
   
   private IResourceManager _resourceManager;
   
   private String _fileDir;
   
   private String _fileName = "connection";
   
   private String _fileExt = ".xml";
   
   private Boolean _writeToFile = true;
   
   private Boolean _checkIfExists = true;
   
   ////
   
   private TextEncryptor _textEncryptor;
   
   ////
   ////
   
   public void init() {
      super.init();
      
      Collection<ConnectionVO> connections = read();
      ConnectionData data;
      for (ConnectionVO connection : connections) {
         data = new ConnectionData(
               connection.getProviderId(),
               connection.getProviderUserId(),
               connection.getDisplayName(),
               connection.getProfileUrl(),
               connection.getImageUrl(),
               getTextEncryptor().decrypt(connection.getAccessToken()),
               getTextEncryptor().decrypt(connection.getSecret()),
               connection.getRefreshToken(),
               connection.getExpireTime());
         
         addConnection(new User(connection.getUserId()),
               getConnectionRegistry().getConnectionFactory(
                     connection.getProviderId()).createConnection(data),
                     false);
      }
   }

   @Override
   public void addConnection(User user, Connection<?> connection) {
      addConnection(user, connection, true);
   }
   
   public void addConnection(User user, Connection<?> connection,
         boolean writeOnFinish) {
      super.addConnection(user, connection);
      if (writeOnFinish) {
         write();
      }
   }
   
   @Override
   public void removeConnections(User user, String providerId) {
      super.removeConnections(user, providerId);
      write();
   }
   
   @Override
   public void removeConnection(User user, ConnectionKey connectionKey) {
      super.removeConnection(user, connectionKey);
      write();
   }
   
   protected Collection<ConnectionVO> compileConnections() {
      Collection<ConnectionVO> connections = new ArrayList<ConnectionVO>();
      Map<Integer, MultiValueMap<String, Connection<?>>> realConnections =
            getConnections();
      ConnectionVO connection;
      for (Entry<Integer, MultiValueMap<String, Connection<?>>> entry : realConnections.entrySet()) {
         connection = new ConnectionVO();
         for (Entry<String, List<Connection<?>>> connectionEntry : entry.getValue().entrySet()) {
            for (Connection<?> conn : connectionEntry.getValue()) {
               ConnectionData data = conn.createData();
               connection.setUserId(entry.getKey());
               connection.setProviderId(connectionEntry.getKey());
               connection.setProviderUserId(data.getProviderUserId());
               connection.setDisplayName(data.getDisplayName());
               connection.setProfileUrl(data.getProfileUrl());
               connection.setImageUrl(data.getImageUrl());
               if (data.getAccessToken() != null) {
                  connection.setAccessToken(
                        getTextEncryptor().encrypt(data.getAccessToken()));
               }
               if (data.getSecret() != null) {
                  connection.setSecret(
                        getTextEncryptor().encrypt(data.getSecret()));
               }
               connection.setRefreshToken(data.getRefreshToken());
               connection.setExpireTime(data.getExpireTime());
               
               connections.add(connection);
            }
         }
      }
      
      return connections;
   }
   
   public synchronized void write() {
      if (!getWriteToFile()) {
         return;
      }
      LOG.info("Writting DAO.");
      
      File file = new File(getFileDir(), getFileName() + getFileExt());
      
      XStream xstream = new XStream();
      applyAlias(xstream);
      
      String xml = xstream.toXML(compileConnections());
      
      FileOutputStream fos = null;
      try {
         fos = new FileOutputStream(file);
         
         fos.write(xml.getBytes());
         fos.flush();
         fos.close();
         
         LOG.debug("File has been saved: " + file);
      } catch (IOException exp) {
         LOG.error(exp.getMessage());
         LOG.debug("Details: ", exp);
      }
   }
   
   @SuppressWarnings("unchecked")
   private Collection<ConnectionVO> read() {
      LOG.info("Loading DAO.");
      
      File file = new File(getFileDir(), getFileName() + getFileExt());
      
      if (getCheckIfExists()) {
         if (!file.exists()) {
            return Collections.emptyList();
         }
      }
      
      InputStream fis = null;
      XStream xstream = new XStream();
      applyAlias(xstream);
      
      Collection<ConnectionVO> results = null;
      try {
         fis = getResourceManager().getResource(file);
         results = (Collection<ConnectionVO>)xstream.fromXML(fis);
         LOG.debug("DAO has been loaded: " + file);
         return results;
      } catch (IOException exp) {
         LOG.error(exp.getMessage());
         LOG.error("Details: ", exp);
         return null;
      }
   }
   
   private void applyAlias(XStream xstream) {
      xstream.alias("connection", ConnectionVO.class);
   }

   /**
    * @return the fileDir
    */
   public String getFileDir() {
      return _fileDir;
   }

   /**
    * @param fileDir the fileDir to set
    */
   public void setFileDir(String fileDir) {
      _fileDir = fileDir;
   }

   /**
    * @return the fileName
    */
   public String getFileName() {
      return _fileName;
   }

   /**
    * @param fileName the fileName to set
    */
   public void setFileName(String fileName) {
      _fileName = fileName;
   }

   /**
    * @return the fileExt
    */
   public String getFileExt() {
      return _fileExt;
   }

   /**
    * @param fileExt the fileExt to set
    */
   public void setFileExt(String fileExt) {
      _fileExt = fileExt;
   }

   /**
    * @return the resourceManager
    */
   public IResourceManager getResourceManager() {
      return _resourceManager;
   }

   /**
    * @param resourceManager the resourceManager to set
    */
   public void setResourceManager(IResourceManager resourceManager) {
      _resourceManager = resourceManager;
   }

   /**
    * @return the writeToFile
    */
   public Boolean getWriteToFile() {
      return _writeToFile;
   }

   /**
    * @param writeToFile the writeToFile to set
    */
   public void setWriteToFile(Boolean writeToFile) {
      _writeToFile = writeToFile;
   }

   /**
    * @return the textEncryptor
    */
   public TextEncryptor getTextEncryptor() {
      return _textEncryptor;
   }

   /**
    * @param textEncryptor the textEncryptor to set
    */
   public void setTextEncryptor(TextEncryptor textEncryptor) {
      _textEncryptor = textEncryptor;
   }

   /**
    * @return the checkIfExists
    */
   public Boolean getCheckIfExists() {
      return _checkIfExists;
   }

   /**
    * @param checkIfExists the checkIfExists to set
    */
   public void setCheckIfExists(Boolean checkIfExists) {
      _checkIfExists = checkIfExists;
   }

}
